﻿//////////////////////////////////////////////////////////////////////////////////////////////////////
//Name: Bartosz
//FullName: Adamczewski
//Project: InternalClasses
//Component: InternalClasses
//StartDate: 2009-08-17 19:29:04
//EndDate: 2009-08-17 19:29:04
//Description: The implementation of InternalClasses component.
//Rights:  Copyright (c) Bartosz Adamczewski
////////////////////////////////////////////////////////////////////////////////////////////////////

#region imports.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Reflection.Emit;
using System.Reflection;

#endregion

namespace InternalClasses.Dynamic
{
    /// <summary>
    /// The Dynamic Type class.
    /// </summary>
    public class DynamicType : DynamicBase, IDynamicTypeBuilder
    {
        private TypeBuilder typeBuilder;
        private AssemblyBuilder assemblyBuilder;
        private Type interfaceType;
        private Type objectType;
        private Type resultType;
        private ILGenerator ilGenerator;
        private List<InjectionMethodDescriptor> methodInjectionList;
        private MethodInfo method;

        /// <summary>
        /// Defines the Type.
        /// </summary>
        /// <param name="sourceType">sourceType.</param>
        /// <param name="interfaceType">interfaceType.</param>
        /// <returns>InternalClasses.Dynamic.IDynamicTypeBuilder.</returns>
        public IDynamicTypeBuilder DefineType(Type sourceType, Type interfaceType)
        {
            this.objectType = sourceType;
            this.interfaceType = interfaceType;

            CreateTypeEntry();
            CreateConstructors();

            return this;
        }

        /// <summary>
        /// Creates the Type Entry.
        /// </summary>
        private void CreateTypeEntry()
        {
            AssemblyName assemblyName = new AssemblyName("DynamicTypeAssembly");
            assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
            
            typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public, objectType, new Type[] { interfaceType });
        }

        /// <summary>
        /// Creates the Constructors.
        /// </summary>
        private void CreateConstructors()
        {
            ConstructorBuilder constructorBuilder = null;

            foreach (var constructor in objectType.GetConstructors())
            {
                constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, constructor.GetParameters().Select(x => (x.ParameterType)).ToArray());

                ilGenerator = constructorBuilder.GetILGenerator();

                ilGenerator.Emit(OpCodes.Ldarg_0);

                ParameterInfo[] parametersInfo = constructor.GetParameters();

                for (int i = 0; i < parametersInfo.Length; i++)
                {
                    if (parametersInfo[i].GetType().IsByRef)
                    {
                        ilGenerator.Emit(OpCodes.Ldarga_S, i + 1);
                    }
                    else
                        ilGenerator.Emit(OpCodes.Ldarg_S, i + 1);
                }

                ilGenerator.Emit(OpCodes.Call, constructor);
                ilGenerator.Emit(OpCodes.Ret);
            }
        }

        /// <summary>
        /// Creates the Injected Method Call.
        /// </summary>
        /// <param name="methodInjectionList">methodInjectionList.</param>
        /// <param name="injectionType">injectionType.</param>
        /// <param name="counter">counter.</param>
        /// <returns>System.Int32.</returns>
        private int CreateInjectedMethodCall(List<InjectionMethodDescriptor> methodInjectionList, InjectionType injectionType, int counter)
        {
            for (int i = 0; i < methodInjectionList.Count; i++)
            {
                if (methodInjectionList[i].InjectionType == injectionType)
                {
                    if (methodInjectionList[i].MethodToInject.GetParameters().Length < 2)
                    {
                        throw new DynamicException("Injection method signature wrong");
                    }

                    CreateArray(ilGenerator, methodInjectionList[i].ExternalParameters, counter + 1);

                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldloc_0);
                    ilGenerator.Emit(OpCodes.Ldloc_S, counter + 1);

                    ilGenerator.EmitCall(OpCodes.Call, methodInjectionList[i].MethodToInject, null);
                    counter++;
                }
            }

            return counter;
        }

        #region IDynamicTypeBuilder Members

        /// <summary>
        /// Opens the Method For Creation.
        /// </summary>
        /// <param name="method">method.</param>
        /// <returns>InternalClasses.Dynamic.IDynamicTypeBuilder.</returns>
        public IDynamicTypeBuilder OpenMethodForCreation(MethodInfo method)
        {
            MethodBuilder builder = typeBuilder.DefineMethod(method.Name, method.Attributes, method.CallingConvention, method.ReturnType, method.GetParameters().Select(p => p.ParameterType).ToArray());
            ilGenerator = builder.GetILGenerator();
            methodInjectionList = new List<InjectionMethodDescriptor>();
            this.method = method;
            return this;
        }

        /// <summary>
        /// Adds the Injected Method.
        /// </summary>
        /// <param name="methodToInject">methodToInject.</param>
        /// <param name="injectionType">injectionType.</param>
        /// <param name="externalParameters">externalParameters.</param>
        /// <returns>InternalClasses.Dynamic.IDynamicTypeBuilder.</returns>
        public IDynamicTypeBuilder AddInjectedMethod(MethodInfo methodToInject, InjectionType injectionType, object[] externalParameters)
        {
            methodInjectionList.Add(new InjectionMethodDescriptor() { MethodToInject = methodToInject, InjectionType = injectionType, ExternalParameters = externalParameters });
            return this;
        }

        /// <summary>
        /// Creates the Method.
        /// </summary>
        /// <returns>InternalClasses.Dynamic.IDynamicTypeBuilder.</returns>
        public IDynamicTypeBuilder CreateMethod()
        {
            ParameterInfo[] parameters = method.GetParameters();

            ilGenerator.DeclareLocal(typeof(object[]));
            ilGenerator.Emit(OpCodes.Ldc_I4_S, parameters.Length);
            ilGenerator.Emit(OpCodes.Newarr, typeof(object));
            ilGenerator.Emit(OpCodes.Stloc_0);

            for (int i = 0; i < parameters.Length; i++)
            {
                ilGenerator.Emit(OpCodes.Ldloc_0);
                ilGenerator.Emit(OpCodes.Ldc_I4_S, i);
                ilGenerator.Emit(OpCodes.Ldarg_S, i + 1);

                if (parameters[i].ParameterType.IsValueType)
                {
                    ilGenerator.Emit(OpCodes.Box, parameters[i].ParameterType);
                }

                ilGenerator.Emit(OpCodes.Stelem_Ref);
            }

            int counter = 0;

            counter = CreateInjectedMethodCall(methodInjectionList, InjectionType.BeforeSource, counter);

            ilGenerator.Emit(OpCodes.Ldarg_0);

            for (int i = 0; i < parameters.Length; i++)
            {
                ilGenerator.Emit(OpCodes.Ldloc_0);
                EmitInt(ilGenerator, i);
                ilGenerator.Emit(OpCodes.Ldelem_Ref);
                EmitCast(ilGenerator, parameters[i].ParameterType);
            }

            ilGenerator.EmitCall(OpCodes.Call, method, null);

            counter = CreateInjectedMethodCall(methodInjectionList, InjectionType.AfterSource, counter);

            if (method.ReturnType != typeof(void))
            {
                LocalBuilder localReturnObj = ilGenerator.DeclareLocal(typeof(object));
                LocalBuilder localReturnType = ilGenerator.DeclareLocal(method.ReturnType);

                if (method.ReturnType.IsValueType)
                {
                    ilGenerator.Emit(OpCodes.Box, method.ReturnType);
                }
                
                ilGenerator.Emit(OpCodes.Stloc_S, localReturnObj.LocalIndex);
                ilGenerator.Emit(OpCodes.Ldloc_S, localReturnObj.LocalIndex);
                EmitCast(ilGenerator, method.ReturnType);
                ilGenerator.Emit(OpCodes.Stloc_S, localReturnType.LocalIndex);
                ilGenerator.Emit(OpCodes.Ldloc_S, localReturnType.LocalIndex);

            }

            ilGenerator.Emit(OpCodes.Ret);

            return this;
        }

        /// <summary>
        /// Creates the Instance.
        /// </summary>
        /// <returns>System.Object.</returns>
        public object CreateInstance()
        {
            CreateType();
            return Activator.CreateInstance(resultType);
        }

        /// <summary>
        /// Creates the Instance.
        /// </summary>
        /// <param name="parameters">parameters.</param>
        /// <returns>System.Object.</returns>
        public object CreateInstance(params object[] parameters)
        {
            CreateType();
            return Activator.CreateInstance(resultType, parameters);
        }

        /// <summary>
        /// Creates the Type.
        /// </summary>
        /// <returns>System.Type.</returns>
        public Type CreateType()
        {
            resultType = typeBuilder.CreateType();
            return resultType;
        }

        #endregion
    }
}
